/**
    Copyright (c) 2010 yakiimo02
    Distributed under the New BSD License.
    See included license.txt or http://www.yakiimo3d.com/NewBSDLicense.txt
**/

/**
    LICENSE

    Copyright (c) 2006-2008 Kevin Beason (kevin.beason@gmail.com)

    Permission is hereby granted, free of charge, to any person obtaining
    a copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
    without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to
    the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include "DXUT.h"
#include "DXHelper.h"
#include "DataBufferDX11.h"
#include "reference/SmallPtCPU.h"
#include "reference/SmallPtGeometry.h"
#include "reference/SmallPtStructs.h"

namespace REF
{

inline bool intersect( const Ray &r, SPT_FLOAT &t, int &id )
{
    SPT_FLOAT n=sizeof(spheres)/sizeof(Sphere);
    SPT_FLOAT d;
    SPT_FLOAT inf=t=1e20;

    for(int i=int(n);i--;) 
    {
        d = spheres[i].intersect( r, ( id != i ) );
        if( d!=0.0 && d<t )
        {
            t=d;
            id=i;
        }
    }

    return t<inf;
}

Vec radiance( const Ray &r_, int depth )
{
    SPT_FLOAT distToIntersect;              // distance to intersection
    int id=-1;                              // id of intersected object
    Ray ray=r_;
    // L0 = Le0 + f0*(L1)
    //    = Le0 + f0*(Le1 + f1*L2)
    //    = Le0 + f0*(Le1 + f1*(Le2 + f2*(L3))
    //    = Le0 + f0*(Le1 + f1*(Le2 + f2*(Le3 + f3*(L4)))
    //    = ...
    //    = Le0 + f0*Le1 + f0*f1*Le2 + f0*f1*f2*Le3 + f0*f1*f2*f3*Le4 + ...
    Vec cl(0,0,0);  // accumulated color
    Vec cf(1,1,1);  // accumulated reflectance
    
    for( ; ; )
    //while (1)
    {
        // Not necessary when using doubles.
        //ray.o = ray.o + ray.d*0.001;
        if ( !intersect(ray, distToIntersect, id) ) {
            // if miss, return black
            return cl; 
        }

        const Sphere &obj = spheres[id];        // the hit object
        cl = cl + cf.mult(obj.emission);
        
        Vec x=ray.o+ray.d*distToIntersect;
        Vec n=(x-obj.position).norm();
        Vec nl=n.dot(ray.d)<0?n:n*-1;
        Vec f=obj.color;
        //SPT_FLOAT p = f.x>f.y && f.x>f.z ? f.x : f.y>f.z ? f.y : f.z; // max refl

        //if (++depth>5) if (drand48()<p) f=f*(1/p); else return cl; //R.R.
        if (++depth>5) {
            // Same as SmallPtGPU remove Russian Roulette
            return cl; 
        }

        cf = cf.mult(f);
        // Ideal DIFFUSE reflection
        if (obj.refl == DIFF)
        {                  
            SPT_FLOAT r1=2*M_PI_REF*drand48();
            SPT_FLOAT r2=drand48();
            SPT_FLOAT r2s=sqrt(r2);
            
            Vec w = nl;
            Vec u = ((fabs(w.x)>.1?Vec(0,1):Vec(1))%w).norm();
            Vec v = w%u;
            Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2)).norm();
            ray = Ray(x,d);
            continue;
        }
        // Ideal SPECULAR reflection
        else if (obj.refl == SPEC)
        {           
            ray = Ray(x,ray.d-n*2.0*n.dot(ray.d));
            continue;
        }
        
        Ray reflRay(x, ray.d-n*2.0*n.dot(ray.d));      // Ideal dielectric REFRACTION
        bool into = n.dot(nl)>0;                       // Ray from outside going in?
        SPT_FLOAT nc=1.0;
        SPT_FLOAT nt=1.5;
        SPT_FLOAT nnt=into?nc/nt:nt/nc;
        SPT_FLOAT ddn=ray.d.dot(nl);
        SPT_FLOAT cos2t;
        
        // Total internal reflection
        if( (cos2t=1.0-nnt*nnt*(1-ddn*ddn))<0.0 )
        {    
            ray = reflRay;
            continue;
        }
        
        Vec tdir = (ray.d*nnt - n*((into?1:-1)*(ddn*nnt+sqrt(cos2t)))).norm();
        
        SPT_FLOAT a=nt-nc;
        SPT_FLOAT b=nt+nc;
        SPT_FLOAT R0=a*a/(b*b);
        SPT_FLOAT c = 1-(into?-ddn:tdir.dot(n));
        
        SPT_FLOAT Re=R0+(1-R0)*c*c*c*c*c;
        SPT_FLOAT Tr=1-Re;
        SPT_FLOAT P=.25+.5*Re;
        SPT_FLOAT RP=Re/P,TP=Tr/(1.0-P);
        
        // reflection
        if (drand48()<P)
        {      
            cf = cf*RP;
            ray = reflRay;
        } 
        // refraction
        else 
        {                 
            cf = cf*TP;
            ray = Ray( x, tdir );
        }
    }
}

/**
*/
SmallPtCPU::SmallPtCPU()
{
}

/**
*/
SmallPtCPU::~SmallPtCPU()
{
}

/**
*/
void SmallPtCPU::Create( DataBufferDX11* pDataBuffer )
{
    int nDataCount = pDataBuffer->GetCount();
    m_buffer.clear();
    m_buffer.resize( nDataCount );
    for( int nIndex=0; nIndex<nDataCount; ++nIndex )
    {
        m_buffer[ nIndex ] = Vec( 0.0f, 0.0f, 0.0f );
    }
}

/**
*/
void SmallPtCPU::Render( ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, DataBufferDX11* pDataBuffer )
{    
    const unsigned int nWidth = static_cast< unsigned int >( pDataBuffer->GetWidth() );
    const unsigned int nHeight = static_cast< unsigned int >( pDataBuffer->GetHeight() );
        
    int samps = 1;

    // camera is fixed
    Ray cam(Vec(50,52,295.6), Vec(0,-0.042612,-1).norm()); // cam pos, dir

    Vec cx=Vec( nWidth*.5135 / nHeight );
    Vec cy=(cx%cam.d).norm()*.5135;

    for(unsigned int y=0; y<nHeight; y++)                       
    {
        for(unsigned int x=0; x<nWidth; x++)   
        {
            unsigned int i=(nHeight-y-1)*nWidth+x;

            // 2x2 subpixel rows
            for(int sy=0; sy<2; sy++)     
            {
                // 2x2 subpixel cols
                for (int sx=0; sx<2; sx++)
                {        
                    Vec r= Vec();
                    for (int s=0; s<samps; s++)
                    {
                        SPT_FLOAT r1=2*drand48();
                        SPT_FLOAT dx=r1<1 ? sqrt(r1)-1: 1-sqrt(2-r1); // [-1,1]
                        SPT_FLOAT r2=2*drand48();
                        SPT_FLOAT dy=r2<1 ? sqrt(r2)-1: 1-sqrt(2-r2); // [-1,1]
                        
                        Vec d = cx*( ( (sx+.5 + dx)/2.0 + x)/nWidth - .5) +
                            cy*( ( (sy+.5 + dy)/2.0 + y)/nHeight - .5) + cam.d;
                        
                        r = r + radiance(Ray(cam.o+d*140.0,d.norm()),0)*(1.0/samps);
                    } // Camera rays are pushed ^^^^^ forward to start in interior
                    
                    m_buffer[i] = m_buffer[i] + Vec( r.x, r.y, r.z ) * .25;
                }
            }
        }
    }

    // copy to DataBuffer's cpu buffer
    BufType* buffer = pDataBuffer->GetpData();
    for( unsigned int i=0; i<nWidth*nHeight; ++i ) 
    {
        buffer[ i ].x += static_cast< float >( m_buffer[ i ].x );
        buffer[ i ].y += static_cast< float >( m_buffer[ i ].y );
        buffer[ i ].z += static_cast< float >( m_buffer[ i ].z );
    }

    // copy from cpu to gpu
    pDataBuffer->UpdateBuffer( pd3dDevice, pd3dImmediateContext );
}

/**
*/
void SmallPtCPU::Destroy()
{
}

}